﻿using System;
using System.Drawing;
using System.Linq;
using GraphDrawing2.Core;
using GraphDrawing2.V2.Concrete.Model;
using GraphDrawing2.V2.Graph.Model;

namespace GraphDrawing2.V2.Concrete.States
{
    public class DraggingState : IState
    {
        /// <summary>
        /// Размеры области, в рамках которой перемещение не будет инициировать перетаскивание.
        /// </summary>
        static readonly Size DRAG_TOLERANCE_SIZE = SystemMetrics.GetDragToleranceSize();

        readonly Model2 model;
        readonly PointF originPoint;

        bool isDragging = false;

        PointF previousPoint;

        public DraggingState(Model2 model, ElementHitTest hitResult)
        {
            this.model = model;
            originPoint = previousPoint = model.ViewPort.ToWorld(model.CanvasModel.MousePosition);
            var shape = hitResult.Element;
            if (!shape.IsSelected())
            {
                foreach (var e in model.ElementsModel.GetElements())
                    e.ClearHint(StateHints.SELECTED);
                shape.SetHint(StateHints.SELECTED);
            }
            var zordered = shape as IZOrdered;
            if (zordered != null)
                zordered.BringToFront();
        }

        public void ProcessMouseMove()
        {
            var viewPoint = model.ViewPort.ToWorld(model.CanvasModel.MousePosition);
            if (isDragging || (isDragging = ShouldStartDrag(viewPoint)))
                DoDrag(viewPoint);
            previousPoint = viewPoint;
        }

        public void ProcessMouseUp()
        {
        }

        public void ProcessKeyPressed()
        {
        }

        /// <summary>
        /// Выполняет перетаскивание.
        /// </summary>
        /// <param name="viewPoint">Координаты курсора в системе координат представления.</param>
        void DoDrag(PointF viewPoint)
        {
            foreach (var elements in model.ElementsModel.GetElements().Where(element => element.IsSelected()).OfType<IMoveable>())
                elements.Move(viewPoint.X - previousPoint.X, viewPoint.Y - previousPoint.Y);
            model.View.UpdateView();
        }

        /// <summary>
        /// Определяет, должно ли начаться перетаскивание при перемещении курсора мыши.
        /// </summary>
        /// <param name="viewPoint">Текущие координаты курсора в системе координат представления.</param>
        /// <returns>Возвращает true, если перетаскивание должно начаться, иначе false.</returns>
        bool ShouldStartDrag(PointF viewPoint)
        {
            return Math.Abs(originPoint.X - viewPoint.X) >= DRAG_TOLERANCE_SIZE.Width || (Math.Abs(originPoint.Y - viewPoint.Y) >= DRAG_TOLERANCE_SIZE.Height);
        }
    }
}